/*
 * Cache.java
 *
 * Created on Jan 9, 2012
 *
 * Copyright (c) 2011 Artur Rataj.
 *
 * This code is distributed under the terms of the GNU Library
 * General Public License, either version 3 of the license or, at
 * your option, any later version.
 */

package pl.gliwice.iitis.spatialnn.propagate;

import java.util.*;
import pl.gliwice.iitis.graphutils.Point;

import pl.gliwice.iitis.spatialnn.network.*;

/**
 * A cache of a given network, for faster propagation in
 * <code>Propagate.get</code>.
 * 
 * @author Artur Rataj
 */
public class Cache {
    /**
     * Network, whose properties are precomputed.
     */
    public Network network;
    /**
     * An output membrane.
     */
    public Membrane outM;
    /**
     * Input buffer of the output membrane.
     */
    public double[] inB;
    /**
     * Output buffers of the input membranes.
     */
    public Map<Membrane, double[]> outB;
    /**
     * Signals, keyed with keys of neurons.
     */
    public Map<Integer, Signal> signals;
    /**
     * Membrane output signals. Signals sorted as in <code>Membrane.copnnections</code>.
     */
    public Map<Membrane, Signal[]> outS;
    /**
     * Indices of inputs of neurons for subequent output signals of input membranes. The
     * order in the lists matches <code>outS</code>.
     */
    public Map<Membrane, int[]> outI;
    /**
     * Signals in subsequent layers.
     */
    public Map<Integer, Map<Neuron, Signal>> layers;
    
    /**
     * Creates a new instance of <code>Cache</code>.
     * 
     * @param network the network, whose properties are to be precomputed
     */
    public Cache(Network network) {
        this.network = network;
        outM = this.network.getOutMembrane();
        computeLayers();
        computeBuffers();
    }
    /**
     * Creates ordered lists of components for faster propagation.
     */
    private void computeLayers() {
        signals = new HashMap<>();
        layers = new HashMap<>();
        int maxLayer = network.getMaxLayer();
        for(int layerNum = 0; layerNum <= maxLayer; ++layerNum) {
            List<Neuron> neurons = network.getLayerNeurons(layerNum);
            Map<Neuron, Signal> layer = new HashMap<>();
            int count = 0;
            for(Neuron n : neurons) {
                Signal s = new Signal(n);
                signals.put(n.key, s);
            }
            for(Neuron n : neurons) {
                Signal s = signals.get(n.key);
                for(AbstractTarget t : n.output) {
                    Component c = network.getComponent(t);
                    if(c instanceof Membrane) {
                        Membrane to = (Membrane)c;
                        s.addOut(to, t.index);
                    } else if(c instanceof Neuron) {
                        Neuron to = (Neuron)c;
                        s.addOut(signals.get(to.key), t.index);
                    } else
                        throw new RuntimeException("unknown component type");
                }
                layer.put(n, s);
            }
            layers.put(layerNum, layer);
        }
        outS = new HashMap<>();
        outI = new HashMap<>();
        for(Membrane m : network.membranes)
            switch(m.type) {
                case IN:
                {
                    int num = m.connections.length;
                    Signal[] s = new Signal[num];
                    int[] i = new int[num];
                    for(int count = 0; count < num; ++count) {
                        NeuronTarget t = (NeuronTarget)m.connections[count];
                        Neuron n = network.neuronMap.get(t.key);
                        if(t.index == 2)
                            n = n;
                        s[count] = signals.get(n.key);
                        i[count] = t.index;
                    }
                    outS.put(m, s);
                    outI.put(m, i);
                    break;
                }  
                case OUT:
                    break;
                    
                default:
                    throw new RuntimeException("unknown membrane type");
            }
    }
    /**
     * Computes needed sizes of membrane buffers, and allocates them.
     */
    private void computeBuffers() {
        inB = new double[outM.connections.length];
        outB = new HashMap<>();
        for(Membrane m : network.membranes) {
            int num;
            switch(m.type) {
                case IN:
                    num = m.connections.length;
                    break;
                    
                case OUT:
                    num = m.getRes();
                    break;
                    
                default:
                    throw new RuntimeException();
            }
            outB.put(m, new double[num]);
        }
    }
}
